﻿namespace Attacher.HotKeys
{
	using System;
	using System.Collections.Generic;
	using System.Linq;
	using System.Windows.Interop;

	internal class HotKeyService : IDisposable, IHotKeyService
	{
		#region Attributes

		private readonly IDictionary<HotKey, Action> hotkeysToActions;
		
		#endregion

		#region Public Methods

		public HotKeyService(IDictionary<HotKey, Action> hotkeysToActions)
		{
			ParameterChecker.IsNotNull(hotkeysToActions, "hotkeysToActions");

			this.hotkeysToActions = hotkeysToActions;

			ComponentDispatcher.ThreadPreprocessMessage += this.ComponentDispatcherOnThreadPreprocessMessageMethod;
		}

		public void RegisterHotKey(HotKey hotKey, Action action)
		{
			ParameterChecker.IsNotNull(hotKey, "hotKey");
			ParameterChecker.IsNotNull(action, "action");

			if (this.hotkeysToActions.ContainsKey(hotKey))
			{
				throw new ArgumentException("The hotKey has been already registered");
			}
			else
			{
				if (!NativeMethods.RegisterHotKey(IntPtr.Zero, hotKey.GetHashCode(), hotKey.ModifierKeys, hotKey.Key))
				{
					throw new InvalidOperationException("The key combination is already registered");
				}
				else
				{
					this.hotkeysToActions.Add(hotKey, action);
				}
			}
		}

		public void UnRegisterHotKey(HotKey hotKey)
		{
			ParameterChecker.IsNotNull(hotKey, "hotKey");

			if (!this.hotkeysToActions.ContainsKey(hotKey))
			{
				throw new ArgumentException("The hotKey has not been registered");
			}
			else
			{
				if (!NativeMethods.UnregisterHotKey(IntPtr.Zero, hotKey.GetHashCode()))
				{
					throw new InvalidOperationException("The key combination cannot be unregistered.");
				}
				else
				{
					this.hotkeysToActions.Remove(hotKey);
				}
			}
		}

		#endregion

		#region Event Handlers

		private void ComponentDispatcherOnThreadPreprocessMessageMethod(ref MSG msg, ref bool handled)
		{
			if (!handled)
			{
				if (msg.message == NativeMethods.WmHotKey)
				{
					int hotKeyHashCode = (int)(msg.wParam);

					KeyValuePair<HotKey, Action> registeredHotKeyAndAction = this.hotkeysToActions.Single(h => h.Key.GetHashCode() == hotKeyHashCode);

					registeredHotKeyAndAction.Value.Invoke();

					handled = true;
				}
			}
		}

		#endregion

		#region IDisposable

		/// <summary>
		/// Gets a value indicating whether this <see cref="HotKeyService"/> is disposed.
		/// </summary>
		/// <value><c>true</c> if disposed; otherwise, <c>false</c>.</value>
		protected bool Disposed { get; private set; }

		/// <summary>
		/// Releases unmanaged and - optionally - managed resources
		/// </summary>
		/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
		private void Dispose(bool disposing)
		{
			if (!this.Disposed)
			{
				this.InternalDispose(disposing);
			}

			this.Disposed = true;
		}

		/// <summary>
		/// Internals the dispose.
		/// </summary>
		/// <param name="disposing">if set to <c>true</c> [disposing].</param>
		protected virtual void InternalDispose(bool disposing)
		{
			if (disposing)
			{
				// Dispose here only managed objects
				ComponentDispatcher.ThreadPreprocessMessage -= this.ComponentDispatcherOnThreadPreprocessMessageMethod;

				List<HotKey> keysToRemove = new List<HotKey>(this.hotkeysToActions.Keys);
				foreach (HotKey hotKey in keysToRemove)
				{
					this.UnRegisterHotKey(hotKey);
				}
			}

			// Dispose here only unmanaged objects 
			// NEVER call here anything managed because maybe 
			// they have been finalized already and you will get an exception
		}

		/// <summary>
		/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
		/// </summary>
		public void Dispose()
		{
			this.Dispose(true);
			GC.SuppressFinalize(this);
		}

		/// <summary>
		/// Finalizes an instance of the SelectableElementCollectionManager class
		/// </summary>
		~HotKeyService()
		{
			this.Dispose(false);
		}

		#endregion
	}
}
